package com.seanLab.tool.es;

import com.seanLab.domain.AbstractImage;
import com.seanLab.domain.Image;
import com.seanLab.dto.SuggestArticleKeywordsDto;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.index.reindex.BulkIndexByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.elasticsearch.xpack.client.PreBuiltXPackTransportClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.util.List;

/**
 * 针对ElasticSearch数据的增删改等功能的实现部分，是针对ES的数据库的操作
 * 主要包括searchWithKeys_TagScores_KeyWeights（关键词的weights和image的tag的score共同加权，返回头部结果）
 * Created by tracy on 17/9/5.
 */
@Component
@ConfigurationProperties(prefix = "com.dasLab.es")
public class ImageTagDao {
    //使用配置文件自动注入属性
    private String host;
    private int port;
    private String index;
    private String type;
    private String cluster_name;
    private Boolean need_security;
    private String user_pwd = "image:fudanes";

    private TransportClient client;
    BulkRequestBuilder bulkRequest;
    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public TransportClient getClient() {
        return client;
    }

    public void setClient(TransportClient client) {
        this.client = client;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getIndex() {
        return index;
    }

    public void setIndex(String index) {
        this.index = index;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCluster_name() {
        return cluster_name;
    }

    public void setCluster_name(String cluster_name) {
        this.cluster_name = cluster_name;
    }

    public Boolean getNeed_security() {
        return need_security;
    }

    public void setNeed_security(Boolean need_security) {
        this.need_security = need_security;
    }

    public String getUser_pwd() {
        return user_pwd;
    }

    public void setUser_pwd(String user_pwd) {
        this.user_pwd = user_pwd;
    }

    public ImageTagDao(){
//        initConnection(host, port);
    }

    /**
     * 初始化链接
     * @param host es服务器地址
     * @param port 端口
     */
    public void initConnection(String host, int port){
        try {
//            Settings settings;
            if (need_security.equals(Boolean.TRUE)) {
//                System.out.println(user_pwd);
                Settings settings = Settings.builder()
                        .put("cluster.name", cluster_name)
                        .put("client.transport.sniff", true)
                        .put("xpack.security.user", user_pwd)
                        .build();
                this.client = new PreBuiltXPackTransportClient(settings)
                        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port));
            } else {
                Settings settings = Settings.builder()
                        .put("cluster.name", cluster_name)
                        .put("client.transport.sniff", true).build();
                this.client = new PreBuiltTransportClient(settings)
                        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port));
            }
            this.bulkRequest=client.prepareBulk();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public int insertImage(Image img) {
//        String json=ImageTag.buildJsonFromImage(img);//TODO
        String json=UtilJson.json2Image(img);
        IndexResponse response = indexsDocument(index, type,json);
        return response.status().getStatus();
    }

    /**
     * 批量插入ImageTag到ES中。
     * @param imageTagList 要插入的ImageTag的list
     * @return 插入结果的状态码
     */
    public int insertImagesbulk(List<? extends AbstractImage> imageTagList) {
        for (int i=0;i<imageTagList.size();i++){
            String line=UtilJson.json2Image(imageTagList.get(i));
            bulkRequest.add(client.prepareIndex(index, type).setSource(line));
        }
        BulkResponse response=bulkRequest.get();
        bulkRequest.request().requests().clear();

//        Fixme return status
//        return response.status().getStatus();
        return 200;
    }

    public IndexResponse indexsDocument(String index, String type, String jsonDoc){
        if (client == null) {
            System.out.println("Null TransportClient");
            return null;
        } else {
            IndexResponse indexResponse = client.prepareIndex(index, type).setSource(jsonDoc).get();
//            System.out.println(indexResponse.toString());
            return indexResponse;
        }
    }

    /**
     * 更新库中的Image，根据ImageID进行定位
     * @param img
     * @return
     */
    public int update(Image img) {//todo

        return 0;
    }

    /**
     * 从ES中删除对应imgID的imageTag
     * @param imgID
     * @return
     */
    public String delete(String imgID) {
        BulkIndexByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
                        .filter(QueryBuilders.termQuery("img_id", imgID))
                        .source(index)
                        .get();
//        DeleteResponse response = client.prepareDelete(index, type, ).get();
        System.out.println(response.toString());
        return response.toString();
    }
    @Deprecated
    public String searchWithKeys(List<String> tagsList, int resultNum) {
        String searchKeyWords=getKeyWords(tagsList);

        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));
//        System.out.println(query.toString());
        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total))
                .setSize(resultNum)
                .get();
        return response.toString();
    }

    /**
     * 利用query文档的关键词的weights和image的tag的score共同加权，计算相关度。
     * 对tag的score做了log2的降权处理，查询关键词的weights未降权
     * @param keywords
     * @param resultNum  返回的结果数
     * @return
     */
    public String searchWithKeys_TagScores_KeyWeights(List<SuggestArticleKeywordsDto> keywords, int resultNum) {
//        List<MatchQueryBuilder> keysQueryList=new ArrayList<MatchQueryBuilder>(keywords.size());
        BoolQueryBuilder keysQuery=QueryBuilders.boolQuery();
        for (int i=0;i<keywords.size();i++){
            SuggestArticleKeywordsDto keyword=keywords.get(i);
            MatchQueryBuilder q=QueryBuilders.matchQuery("Tags.tag_name",keyword.getKeywords())
                    .boost((float)keyword.getScore());
            keysQuery.should(q);
        }//OPTION1 add modifier as log2 key_weight*log(2+tag_score)*tfidf
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(keysQuery,ScoreFunctionBuilders
                        .fieldValueFactorFunction("Tags.tag_score").modifier(FieldValueFactorFunction.Modifier.LOG2P));

//OPTION2 key_weight*log(2+tag_score)*tfidf+10,000
        Script function_script=new Script("_score*Math.log(2 + doc['Tags.tag_score'].value)/Math.log(2)");

        FunctionScoreQueryBuilder script_query =
                QueryBuilders.functionScoreQuery(keysQuery,ScoreFunctionBuilders
                        .scriptFunction(function_script));

//-------------------------------
//        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total);
        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", script_query , ScoreMode.Total);


        BoolQueryBuilder booleanQuery=QueryBuilders.boolQuery()
                .must(nestedquery)
                .mustNot(QueryBuilders.termQuery("img_source","wiki"));

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(booleanQuery)
                .setSize(resultNum)
                .get();
        return response.toString();
    }

    /***
     * 利用query文档的关键词的weights和image的tag的score共同加权, 并过滤出包含topic的结果，返回头部resultNum个
     * @param keywords 搜索关键词及权重
     * @param resultNum 结果数量
     * @param filter topic过滤
     * @return
     */
    public String searchWithKeys_TagScores_KeyWeights_TopicFilter(List<SuggestArticleKeywordsDto> keywords, List<Integer> filter, int resultNum) {
//        List<MatchQueryBuilder> keysQueryList=new ArrayList<MatchQueryBuilder>(keywords.size());
        BoolQueryBuilder keysQuery=QueryBuilders.boolQuery();
        for (int i=0;i<keywords.size();i++){
            SuggestArticleKeywordsDto keyword=keywords.get(i);
            MatchQueryBuilder q=QueryBuilders.matchQuery("Tags.tag_name",keyword.getKeywords())
                    .boost((float)keyword.getScore());
            keysQuery.should(q);
        }
//OPTION1 add modifier as log2 key_weight*log(2+tag_score)*tfidf
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(keysQuery,ScoreFunctionBuilders
                        .fieldValueFactorFunction("Tags.tag_score").modifier(FieldValueFactorFunction.Modifier.LOG2P));

//OPTION2 key_weight*log(2+tag_score)*tfidf+10,000
        Script function_script=new Script("_score*Math.log(2 + doc['Tags.tag_score'].value)/Math.log(2)");
//        Script function_script=new Script("if ((img_source)=='wiki') {_score*10000} else {_score*Math.log(2 + doc['Tags.tag_score'].value)/Math.log(2)}");


        FunctionScoreQueryBuilder script_query =
                QueryBuilders.functionScoreQuery(keysQuery,ScoreFunctionBuilders
                        .scriptFunction(function_script));

//-------------------------------
//        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total);
        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", script_query , ScoreMode.Total);

        BoolQueryBuilder topicFilter=QueryBuilders.boolQuery()
                .should(QueryBuilders.termsQuery("top_topics",filter));
//                .should(QueryBuilders.termQuery("img_source","wiki"));


        BoolQueryBuilder booleanQuery=QueryBuilders.boolQuery()
                .must(nestedquery)
                .mustNot(QueryBuilders.termQuery("img_source","wiki"))
                .filter(topicFilter);

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(booleanQuery)
                .setSize(resultNum)
                .get();
        return response.toString();
    }

    /**
     * 针对专题图库的搜索，返回resultNum个结果。
     * 由于专题图库的图片的tag没有score，因此只根据查询关键词的权重来计算
     * @param keywords
     * @param resultNum 返回的结果数
     * @return top resultNum of wiki img
     */
    public String searchWiki(List<SuggestArticleKeywordsDto> keywords, int resultNum) {
        BoolQueryBuilder keysQuery=QueryBuilders.boolQuery();
        for (int i=0;i<keywords.size();i++){
            SuggestArticleKeywordsDto keyword=keywords.get(i);
            MatchQueryBuilder q=QueryBuilders.matchQuery("Tags.tag_name",keyword.getKeywords())
                    .boost((float)keyword.getScore());
            keysQuery.should(q);
        }
        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", keysQuery , ScoreMode.Total);

        BoolQueryBuilder wikiFilter=QueryBuilders.boolQuery()
                .should(QueryBuilders.termQuery("img_source","wiki"));

        BoolQueryBuilder booleanQuery=QueryBuilders.boolQuery()
                .must(nestedquery)
                .filter(wikiFilter);

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(booleanQuery)
                .setSize(resultNum)
                .get();
        return response.toString();

    }

    /**
     * 只利用query文档的关键词weights，忽略img中tag的score
     * @param keywords
     * @param resultNum
     * @return
     */
    @Deprecated
    public String searchWithKeys_KeyWeights(List<SuggestArticleKeywordsDto> keywords, int resultNum) {
//        List<MatchQueryBuilder> keysQueryList=new ArrayList<MatchQueryBuilder>(keywords.size());
        BoolQueryBuilder keysQuery=QueryBuilders.boolQuery();
        for (int i=0;i<keywords.size();i++){
            SuggestArticleKeywordsDto keyword=keywords.get(i);
            MatchQueryBuilder q=QueryBuilders.matchQuery("Tags.tag_name",keyword.getKeywords())
                    .boost((float)keyword.getScore());
            keysQuery.should(q);
        }

        System.out.println(keysQuery.toString());

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", keysQuery , ScoreMode.Total))
                .setSize(resultNum)
                .get();
        return response.toString();
    }

    /**
     * 只利用query文档的关键词weights，忽略img中tag的score，并通过topic过滤
     * @param tagsList
     * @param filter
     * @param resultNum
     * @return
     */
    @Deprecated
    public String searchWithKeysAndFilter(List<String> tagsList, List<Integer> filter, int resultNum) {
        String searchKeyWords=getKeyWords(tagsList);

        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));
//        System.out.println(query.toString());
        NestedQueryBuilder nestedquery=QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total);

        BoolQueryBuilder topicFilter=QueryBuilders.boolQuery()
                .should(QueryBuilders.termsQuery("top_topics",filter));

        BoolQueryBuilder booleanQuery=QueryBuilders.boolQuery()
                .must(nestedquery)
                .filter(topicFilter);
//        System.out.println(booleanQuery.toString());
        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(booleanQuery)
                .setSize(resultNum)
                .get();

        return response.toString();
    }

    @Deprecated
    public String facetSeach(List<String> tagsList, int resultNum, String name, String field){ //TODO
        String searchKeyWords=getKeyWords(tagsList);
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total))
                .addAggregation(AggregationBuilders.terms(name).field(field))
                .setSize(resultNum)
                .execute().actionGet();

        return response.toString();
    }
    @Deprecated
    public String facetSeachPostFilter(List<String> tagsList, String filter, int resultNum, String name, String field){ //TODO
        String searchKeyWords=getKeyWords(tagsList);

//-------------search result
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));
//
        TermQueryBuilder pf = QueryBuilders.termQuery(field,filter);

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total))
                .addAggregation(AggregationBuilders.terms(name).field(field))
                .setPostFilter(pf)
                .setSize(resultNum)
                .execute().actionGet();

        return response.toString();
    }
    @Deprecated
    public String nestedFacetSearch(List<String> tagsList, int resultNum, String path, String field,String name1,String name2,String name3) {
        String searchKeyWords=getKeyWords(tagsList);
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));

        NestedAggregationBuilder ab=AggregationBuilders.nested(name1,path).subAggregation(AggregationBuilders.terms(name2).field(field).subAggregation(AggregationBuilders.reverseNested(name3)));
        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total))
                .addAggregation(ab)
                .setSize(resultNum)
                .execute().actionGet();
        return response.toString();
    }

    /**
     *
     * @param tagsList 输入的用来搜索的关键词
     * @param filter 搜索结果根据该bucket来过滤
     * @param resultNum 搜索结果的数量
     * @param field 根据field来进行聚合
     * @param name1
     * @param name2
     * @param name3
     * @return
     */
    @Deprecated
    public String nestedFacetSearchPostFilter(List<String> tagsList, String filter, int resultNum, String field,String name1,String name2,String name3) {
        String searchKeyWords=getKeyWords(tagsList);
//        String path="Tags";
        FunctionScoreQueryBuilder query =
                QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("Tags.tag_name",searchKeyWords),
                        ScoreFunctionBuilders.fieldValueFactorFunction("Tags.tag_score"));

        NestedAggregationBuilder na= AggregationBuilders.nested(name1,"Tags").subAggregation(AggregationBuilders.terms(name2).field(field).subAggregation(AggregationBuilders.reverseNested(name3)));


        NestedAggregationBuilder ab =
                AggregationBuilders.nested(name1,"Tags").subAggregation(AggregationBuilders.terms(name2).field(field).subAggregation(AggregationBuilders.reverseNested(name3)));

        NestedQueryBuilder pf =
                QueryBuilders.nestedQuery("Tags",QueryBuilders.termQuery("Tags.tag_source",filter),ScoreMode.None);

        SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setQuery(QueryBuilders.nestedQuery("Tags", query , ScoreMode.Total)).setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .addAggregation(ab)
                .setPostFilter(pf)
                .setSize(resultNum)
                .execute().actionGet();
        return response.toString();
    }

    private String getKeyWords(List<String> tagsList) {
        String ret="";
        for (int i=0;i<tagsList.size();i++){
            ret+=tagsList.get(i)+" ";
        }
        if (ret.length()>0){
            ret=ret.substring(0,ret.length()-1);
        }
        return ret;
    }

}
